Skip to content

Sync addons-source@maintenance/gramps61 with upstream (2026-05-23) - #19

Merged
eduralph merged 43 commits into
maintenance/gramps61from
sync/upstream-maintenance-gramps61-auto
May 29, 2026
Merged

Sync addons-source@maintenance/gramps61 with upstream (2026-05-23)#19
eduralph merged 43 commits into
maintenance/gramps61from
sync/upstream-maintenance-gramps61-auto

Conversation

@eduralph

Copy link
Copy Markdown
Owner

Automated nightly sync from gramps-project/addons-source@maintenance/gramps61. Generated by .github/workflows/upstream-sync.yml on the testbed.

eduralph and others added 8 commits May 20, 2026 12:17
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
eduralph force-pushed the sync/upstream-maintenance-gramps61-auto branch from e79e0fd to 05dc3b1 Compare May 24, 2026 06:53
eduralph and others added 2 commits May 24, 2026 08:03
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
eduralph force-pushed the sync/upstream-maintenance-gramps61-auto branch 2 times, most recently from 943479d to d2ed1ca Compare May 26, 2026 07:34
eduralph and others added 12 commits May 26, 2026 08:57
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
eduralph force-pushed the sync/upstream-maintenance-gramps61-auto branch from d2ed1ca to e3835d8 Compare May 27, 2026 07:54
eduralph and others added 4 commits May 27, 2026 09:15
…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
GaryGriffin and others added 16 commits May 27, 2026 09:39
… 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
eduralph force-pushed the sync/upstream-maintenance-gramps61-auto branch from e3835d8 to cbb7c18 Compare May 28, 2026 07:46
@eduralph
eduralph force-pushed the sync/upstream-maintenance-gramps61-auto branch from cbb7c18 to 07dbfb2 Compare May 29, 2026 07:45
@eduralph
eduralph merged commit dfd437d into maintenance/gramps61 May 29, 2026
6 of 8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants