Sync addons-source@maintenance/gramps61 with upstream (2026-05-23) - #19
Merged
eduralph merged 43 commits intoMay 29, 2026
Merged
Conversation
Bug 0014056-style symptom, this one tracked as bug 0012913. When a
family in the descent chain has only one parent defined,
``family.get_mother_handle()`` returns ``None``, and the existing
``spouse_handle = mother if mother != handle else
family.get_father_handle()`` resolves to ``None`` too. The code
then called ``self.database.get_person_from_handle(None)``, which
raises ``HandleError: Handle is None`` and aborts the entire
report -- exactly the reporter's traceback.
The very next line already had a defensive ``if spouse: ... else:
spouse_name = 'N.N.'`` block, anticipating the missing-spouse case,
but the lookup right above it bypassed that branch by raising
before ``spouse`` was even bound.
Reproduction (per the reporter, against example.gramps):
* Active person: Boucher, David (I0801)
* Ancestor: Boucher, David (I0801)
* Descendant: Boucher, Mary Cecilia (I0055)
* In Family F0037, remove Reeves, Maria (I0052) as mother.
* Run Reports → Text → Lines of Descendancy → crash before the
fix; "N.N." in place of the missing spouse after the fix.
Fix: guard the get_person_from_handle call with ``if
spouse_handle:`` and fall back to ``spouse = None`` on the no-handle
path, so the existing ``'N.N.'`` fallback handles the rest.
Minimal-delta change; preserves existing user-visible behaviour for
the case where the spouse object itself is missing but the handle
isn't.
Test: extend the existing
LinesOfDescendency/tests/test_linesofdescendency_guards.py with a
TestWritePathMissingSpouse class that drives ``write_path``
directly via ``__new__``-bypass (mirrors the gramps-core test style
for guard regressions) and asserts: (1) the call completes without
HandleError, (2) get_person_from_handle is never called with None,
and (3) the rendered output still contains the existing 'N.N.'
fallback for the missing-spouse case.
Closes 12913 (MantisBT).
Forms with many narrow columns (e.g. CA1851EW-A has 42 columns with size=1 entries) caused the page to expand to ~5700 pt (≈2 m wide) via the old formula MIN_COL_W * sum(sizes) / min(size). This made every large column enormous and pushed all but the first heading column off-screen. - Add MAX_COL_W = 100 pt (≈ 20 chars at 8 pt Helvetica). - Replace _required_avail_w with a sum of per-column clamped widths so the page expands only as much as needed to give every column between MIN_COL_W and MAX_COL_W. - Replace _col_widths with an iterative clamp that redistributes space from fixed columns to the remaining free ones, keeping all widths in [MIN_COL_W, MAX_COL_W]. - Use base_avail (A4/landscape) instead of the expanded avail_w when computing heading field widths, and cap each heading field at MAX_COL_W, so all heading fields stay on-screen regardless of how wide the data section needs to be. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
CamelCase attribute names (e.g. WindowRooms, LunaticMale) could not be wrapped by the existing word-wrap logic, causing the clipping rect to silently cut them off in narrow columns. - Add _split_camel() to insert spaces at camelCase boundaries before wrapping, so "WindowRooms" → "Window Rooms" and wraps across two lines. - Hard-truncate any word that still exceeds the column width after splitting, taking min(label_length, entry_width) per line. The label can span the full HEADER_LINES rows to show as much text as possible. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
eduralph
force-pushed
the
sync/upstream-maintenance-gramps61-auto
branch
from
May 24, 2026 06:53
e79e0fd to
05dc3b1
Compare
Three related fixes for the same addon:
1) Plugin descriptor rename:
`SurnameMappingGramplet.grp.py` -> `SurnameMappingGramplet.gpr.py`.
The file is named `.grp.py` (typo). Gramps loads only `*.gpr.py`
plugin descriptors, so the addon has never been registered with
Gramps — it does not appear in the Add Gramplet menu today.
2) Python 2 / Gramps 3 era pre-namespace imports:
import gtk # PyGTK, lowercase
from gen.plug import Gramplet
Neither resolves on Python 3 / Gramps 5+. Once the descriptor is
renamed and Gramps actually tries to register the addon, the
module would fail import with `ModuleNotFoundError: No module
named 'gen'`. Migrate to:
from gi.repository import Gtk
from gramps.gen.plug import Gramplet
Rename every `gtk.*` reference in the file (25 sites) to
`Gtk.*` to match.
3) Python 2 `unicode()` usages:
self.dbstate.db.set_name_group_mapping(unicode(surname), ...)
`unicode` is a Py2 builtin removed in Py3 (the default string
type is already Unicode; it's just `str`). Replace all eight
`unicode(...)` calls with `str(...)`.
PR gramps-project#820's body called out items (1) and (3) under "Renamed
SurnameMappingGramplet.grp.py -> .gpr.py" and "Py2 leftovers:
... unicode() -> str()". Item (2) was the gap an earlier revision
of this PR missed — the .grp.py -> .gpr.py rename without the
import migration would have made the addon visible to Gramps but
still unloadable.
Behavioural impact:
- The rename makes Gramps register a previously-invisible plugin.
Users with this addon installed will see "Surname Mapping"
appear in their Gramplet bar — the original authorial intent.
- The import migration unblocks module loading on Py3.
- The unicode -> str swap is a no-op on Py3 (both call-site
results are identical) and fixes the latent NameError on the
on-edit/remove paths.
Out of scope for this PR (separate follow-up): the addon's
`init()` / `build_gui()` methods still use several PyGTK-era
GTK 2 APIs (`Gtk.Toolbar.insert_stock`, `Gtk.STOCK_*`,
`Gtk.DIALOG_MODAL`, the deprecated `Gtk.Table.attach(... xoptions=...)`
form, etc.) that don't work as-is on modern PyGObject. Those only
fire when a user actually opens the gramplet — they don't block
plugin registration. Fixing them needs a wider GTK 2 -> GTK 3 API
audit best done as a separate PR.
Add a regression test in `SurnameMappingGramplet/tests/` that
imports the module via the explicit submodule path (addon dir and
impl module share the name — namespace-package shadowing, same
trap as libaccess; see gramps bug 0012691 family). Asserts the
`SurnameMappingGramplet` class is a Gramplet subclass.
Verified via the testbed's `run-addon-unit.sh SurnameMappingGramplet`:
Before fix: ModuleNotFoundError: No module named 'gen' (FAIL)
After fix: 1 test, OK
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
eduralph
force-pushed
the
sync/upstream-maintenance-gramps61-auto
branch
2 times, most recently
from
May 26, 2026 07:34
943479d to
d2ed1ca
Compare
The Gtk 3.0 guard added in upstream commit c312cd7 ("Update unittests for 6.1") raises unittest.SkipTest(...) at module level before `import unittest` further down in TMGimporter/tests/test_integration.py (line 26) and TMGimporter/tests/test_libtmg.py (line 24). On a host where gi/Gtk are unavailable, module load fails with NameError instead of skipping cleanly; ruff F821 also reports the undefined name at lint time. Hoist `import unittest` along with the other stdlib imports above the try/except block in both files, matching the shape merged upstream in WebSearch/tests/test_filetable.py (PR 833). No behavioural change when gi/Gtk are present. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The four ngettext() counter calls embedded a trailing \n in the plural form but not the singular. gettext requires a msgid and its msgid_plural to agree on the trailing newline, so msgfmt rejected every catalog that translated these strings and `make.py build lxml` aborted with fatal errors (Mantis bug 14234). Append the layout newline in code instead -- as the pre-39fbcdd version did -- so it is no longer part of the translatable string. The singular case (count == 1) still gets its line break. The per-language catalogs are not touched here: make.py extract-po regenerates them from the corrected template, so the stale \n drops out of de/hr/nl/pt_PT/sk through the normal Weblate pipeline rather than by hand-editing build output. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Provides nine filter rules (Person, Family, Event, Place, Source, Citation, Repository, Media, Note) that match objects whose tag names contain a given substring (case-insensitive). Uses the selected_handles optimizer pattern with find_backlink_handles so only objects of the target namespace are fetched from the database. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Closing a family tree emits 'database-changed'; change_db() clears the history and calls redraw(), which -- finding no active person -- calls change_object(None). That returned immediately, before the code that rebuilds header/stack, so the view kept showing the closed tree's people. Their edit buttons still held handles from the closed database, and clicking one raised gramps.gen.errors.HandleError inside get_person_from_handle() (Mantis bug 12572; the underlying stale-view symptom is Mantis bug 14226). On the no-active-object path, clear header/stack and disable the per-page actions instead of returning early, mirroring the built-in RelationshipView whose change_db() clears its container directly. edit_active() is guarded against the now-possible active_page is None. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
eduralph
force-pushed
the
sync/upstream-maintenance-gramps61-auto
branch
from
May 27, 2026 07:54
d2ed1ca to
e3835d8
Compare
…10512)
## Root cause
The Sandclock Genealogy Tree report rendered only one page of an
arbitrarily-large tree. The LaTeX `genealogytree` package's default
`database` template lays the tree out so widely that big sandclocks
clip off the page; the package itself does not support multi-page
output for that layout (confirmed by SNoiraud in note ~0046612 on
the Mantis ticket).
## Fix
Expose SNoiraud's 2018-12-20 workaround as a user-facing report option.
A new "Compact tree layout" boolean on the Sandclock report appends
`template=database pole reduced` to the `\genealogytree[…]` parameter
list when enabled, giving ~4x more space per page at the cost of
denser per-node formatting.
The directive is appended last so it overrides any node-spacing
defaults that `gramps/gen/plug/docgen/treedoc.py`'s built-in keys
(`level distance`, `node size`, `level size`) set earlier in the
parameter list — pgfkeys is order-sensitive, so a later `template=`
re-sets the keys the template controls.
Option-list assembly was extracted into a `_build_tree_options()`
method so the option-construction logic can be unit-tested without
driving a full report run.
## Verified against
- `GenealogyTree/gt_sandclock.py:148` — `_build_tree_options` body
- `GenealogyTree/gt_sandclock.py:96-128` — `write_report` calls into it
- `GenealogyTree/treeplugins.gpr.py` — unchanged; per addons-source
convention the maintainer manages the `.gpr.py` version, not the
contributor
## Test
`GenealogyTree/tests/test_sandclock_compact_template.py` — five focused
unit tests exercising the new option-list builder via
`SandclockTree.__new__(...)` skeleton (no Gramps GUI, no LaTeX, no DB).
Cover:
* Default layout omits any `template=` directive.
* `compact=True` appends exactly `template=database pole reduced`.
* Directive lands LAST in the option list (pgfkeys order).
* Other default options (`pref code=`, `list separators hang`,
`place text=`, `box=`) remain intact when compact is on.
* `include_images` and `compact` coexist without either suppressing
the other.
All 5 pass via `gramps-testbed/scripts/ubuntu/run-addon-unit.sh
GenealogyTree`.
Fixes #10512
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
main() reads `self.uistate.viewmanager.active_page.bottombar` unguarded, but the gramplet framework keeps stepping the generator via `_updater` → `next(self._generator)` after the family tree is closed. At that point `active_page` is None, so the read raises AttributeError: 'NoneType' object has no attribute 'bottombar'. Pull active_page into a local, return early if it's None, then proceed with the existing bottombar/db-open/count<3 short-circuit chain. No behaviour change while a tree is open. Regression test exercises main() with a stub uistate whose active_page is None and asserts the generator exits cleanly (StopIteration) instead of raising. Two further cases lock the existing dashboard / non-dashboard short-circuit paths so the new guard does not regress them. Fixes #13966
… off The pre-pass guarded by `if self.dubperson:` in write_report() is the only path that fills `self.report_app_ref`. When that option is off but Index of Dates / Places / Names is on, append_event still runs from write_person_info / __write_family_events for every event and raises against the empty / missing table — KeyError on the current handle (this report), or AttributeError on older addon versions before the partial fix for 12857/12859 added the unconditional init. Fall back inside append_event to the current writing-pass coordinates (report_count, generation+1, dnumber[phandle], person's name). That is the natural Ref for an index entry anyway — the location in the document where the event is being narrated. When report_app_ref is populated (the omit-duplicates path), the original `[0]` read is preserved, so behaviour there is unchanged. Regression test exercises append_event in isolation with an empty report_app_ref and asserts (a) it does not raise and (b) the index entry references current state. Fixes #14051
Refines the previous commit. The earlier fallback computed the Ref tuple from the CURRENT writing-pass coordinates each time append_event ran. With omit-duplicates off and a person appearing in multiple per-ascendant reports, append_event runs once per encounter, and `self.index_of_dates[year][date]` / index_of_places overwrites on every call — so the surviving entry ended up tagged with the LAST encounter's coordinates, not the first. The omit-duplicates path emits the FIRST-encounter coordinates (report_app_ref[phandle][0]). Populating that entry on first call in append_event — rather than re-deriving on every call — pins the Ref to the first encounter and makes the two paths semantically equivalent. Verified empirically on example.gramps (I00016): 1365 / 1372 common (place, date) index entries have identical Ref tuples between omit-duplicates ON and OFF; the 7 mismatches all carry `date="0000-00-00"`, a pre-existing key collision in index_of_places[place][date] where unrelated empty-date events overwrite each other (independent of this fix). Place coverage in OFF is slightly higher (658 vs 656) because OFF doesn't early-return duplicates, so events on duplicated people are indexed too. New test TestRefSemanticsParityWithOmitDuplicates locks first- encounter semantics by simulating a multi-encounter scenario.
PluginStatus.__info iterates Requirements().info(addon) as [label, table] pairs and joins the first row with " ".join(req_lst[0]). If the addon listing carries a present-but-empty requires key - e.g. PostgreSQL Enhanced's gpr.py declares `requires_exe=[]`, which ends up as `"re": []` in addons-<lang>.json - gramps core's Requirements.info still emits an "Executables" label paired with an empty table, and the join raises `IndexError: list index out of range`. Skip empty tables cleanly: indexing fails on them and there is nothing useful to render anyway. PostgreSQL Enhanced is currently the only addon in gramps61 listings with a present-but-empty requires key, which matches the reported "only the PostgreSQL Enhanced row crashes" symptom. Resolves #13979.
Running `make.py <ver> listing <Addon>` against an addon whose .gpr.py declares `include_in_listing=False` (or whose .addon.tgz has not been built yet) caused the listings file to be overwritten with `[]`, wiping every previously listed addon for that language. The single-addon update path builds `listings` per language, then either replaces the file (for "listing all") or merges entries from the existing file with the new ones. When the targeted addon yielded no eligible plugin, `listings` was empty, the merge loop did not iterate, and `output` was written as `[]`. Guard the single-addon path: when `listings` is empty and a listings file already exists, skip the write and tell the user that the targeted addon is not eligible and how to remove an existing entry on purpose (`make.py <ver> unlist <Addon>`). Resolves #13694.
GaryGriffin flagged on PR 915 that the listings file still corrupts for addons that ship multiple .gpr.py files (or multiple register() calls per .gpr.py). The Form addon is the canonical example: running `make.py gramps61 listing Form` ballooned addons-en.json from 170 entries to 510 - every existing addon appearing three times, one per Form-registered plugin. Root cause: the merge path's outer `for plugin in sorted(listings, ...)` loop re-read the entire existing listings file on each iteration while accumulating into a shared `output`. With N new plugins for cmd_arg the existing N-1 unrelated entries got appended N times. Replace the per-plugin re-read with a single-pass merge: read the existing file once, drop every row that belongs to cmd_arg (matched by .z) or whose (t, i) collides with one of the fresh plugins, then merge the kept rows with the new plugins in sorted (t, i) order. This also tightens behaviour in two ways: - Stale entries for cmd_arg (e.g. a register() removed from a gpr.py since the last build) are dropped, not preserved. - The "first match wins" ambiguity of the old z+t matcher is gone; matching is per-plugin by (t, i). Tests in `tests/test_make_listing.py` now cover both the 13694 case (include_in_listing=False addon) and a multi-gpr addon shaped like the real Form. The multi-gpr test fails on the pre-fix code with "14 != 6" - exactly the symptom Gary saw - and passes after. Resolves #13694.
The Geneanet entry in WEBSITES (FRWebPack.py:45) pointed at the deprecated `https://search.geneanet.org/result.php?lang=fr&name=...` URL, which no longer returns useful results, and the template only carried the surname. Replace with the current individus search the reporter on Mantis 14145 supplied: https://www.geneanet.org/fonds/individus/?go=1&nom=%(surname)s&prenom=%(given)s The two `%(...)s` placeholders match the file's existing convention (see lines 41, 46, 48) and the libwebconnect URL builder at libwebconnect/libwebconnect.py:182, which formats the pattern with the dict returned by make_person_dict — both `surname` and `given` keys are populated there. callmedave's note 4 on the tracker recommends the WebSearch Gramplet as a longer-term replacement for the Web Connect Pack family of addons, BUT his note 5 explicitly confirms the bug for FrWebConnectPack — so the live addon is still in scope and this is not a wontfix. Add a regression test in `FRWebConnectPack/tests/` that pulls the Geneanet pattern from WEBSITES and applies the same `pattern % dict` formatting libwebconnect uses. Pure string assertion — no network, no display, no Gtk. The test covers both halves of the fix: the URL must include both name parts (the given name was discarded pre-fix) and must target the current host/path/params (the old `search.geneanet.org/result.php` form is asserted absent). Verified via the testbed's `run-addon-unit.sh FRWebConnectPack`: Before fix: FAILED (failures=2) After fix: 2 tests, OK Fixes #14145 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add IE 1926 Form and reference
B/M/D forms from https://gramps-project.org/bugs/view.php?id=13977 Removed Marriage/Signed field
Added new headings for additional attributes in the form. Added new headings and reordered them.
eduralph
force-pushed
the
sync/upstream-maintenance-gramps61-auto
branch
from
May 28, 2026 07:46
e3835d8 to
cbb7c18
Compare
eduralph
force-pushed
the
sync/upstream-maintenance-gramps61-auto
branch
from
May 29, 2026 07:45
cbb7c18 to
07dbfb2
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Automated nightly sync from
gramps-project/addons-source@maintenance/gramps61. Generated by .github/workflows/upstream-sync.yml on the testbed.