Follow-up to the admin audit (#1346, Phase 4 deferred this as its own focused issue because it's destructive).
Problem
Keywords are free-text and case/whitespace-sensitive with no uniqueness constraint, so near-duplicates coexist (e.g. Speech / speech / Speech ) and fragment the public keyword pages. Phase 4 (PR #1351) added a sortable Total Uses column and a Used / Unused filter to find orphans/dupes; this issue adds the tool to merge them.
Proposed feature
A "Merge selected keywords" admin action on the Keyword changelist:
- Select two or more keywords → choose the action.
- Intermediate confirmation page (standard Django action pattern) listing the selected keywords with their usage counts, and a radio to pick the target keyword to merge into.
- On confirm: for every model that references Keyword — Publication, Talk, Poster, Grant, Project, ProjectUmbrella (the 6
keywords M2M holders; Video has none) — reattach the target keyword to each object currently tagged with a non-target keyword, then delete the non-target keywords.
Implementation notes
- Reattach via the M2M managers (
obj.keywords.add(target) is idempotent, then the source keyword's deletion drops its rows) to avoid unique-constraint collisions when an object already has the target.
- Needs a small template under
website/admin/templates/admin/website/keyword/ for the intermediate page.
- Tests required given it's destructive: target keeps/gains all references, sources are deleted, no duplicate M2M rows, objects already tagged with the target are unaffected, and a no-op/edge case (single keyword selected) is handled.
Acceptance
Parent: #1346.
Follow-up to the admin audit (#1346, Phase 4 deferred this as its own focused issue because it's destructive).
Problem
Keywords are free-text and case/whitespace-sensitive with no uniqueness constraint, so near-duplicates coexist (e.g.
Speech/speech/Speech) and fragment the public keyword pages. Phase 4 (PR #1351) added a sortable Total Uses column and a Used / Unused filter to find orphans/dupes; this issue adds the tool to merge them.Proposed feature
A "Merge selected keywords" admin action on the Keyword changelist:
keywordsM2M holders; Video has none) — reattach the target keyword to each object currently tagged with a non-target keyword, then delete the non-target keywords.Implementation notes
obj.keywords.add(target)is idempotent, then the source keyword's deletion drops its rows) to avoid unique-constraint collisions when an object already has the target.website/admin/templates/admin/website/keyword/for the intermediate page.Acceptance
Parent: #1346.