feat: decouple spp_programs from core API / Studio / audit / source-tracking (#1080)#231
feat: decouple spp_programs from core API / Studio / audit / source-tracking (#1080)#231emjay0921 wants to merge 8 commits into
Conversation
…, #1082) Extract the Program and ProgramMembership REST API out of spp_api_v2 into a new spp_api_v2_programs companion (auto-installs when spp_api_v2 + spp_programs are both present), so spp_api_v2 no longer DIRECTLY depends on spp_programs. Moved into the companion: program / program_membership routers, services, schemas, filter configs, api.path records, the program scope-enforcement test, and the program filter routers (registered into the shared RESOURCE_SERVICES). Base spp_api_v2 drops the program imports, the spp_programs dependency, and the program data records. #1082: spp_api_v2_data has no program code of its own; it only pulled spp_programs transitively via spp_api_v2. The remaining transitive path (spp_api_v2 -> spp_source_tracking -> spp_programs) closes in #1084.
Remove the two hard spp.program references from spp_studio so it no longer DIRECTLY depends on spp_programs: - program_ids (Many2many spp.program) on spp.studio.mixin - program_id (Many2one spp.program) on the pack-install wizard Both move to a new spp_studio_programs companion (auto-installs when spp_studio + spp_programs are both present). The base wizard now resolves the program via a _get_pack_program_id() hook (None in base; the companion returns the selected program). Note: spp_studio still pulls spp_programs transitively via spp_audit, which also depends on spp_programs and is not yet split.
…rce_tracking_programs (#1084) Move the spp.program.membership source-tracking extension out of spp_source_tracking into a new spp_source_tracking_programs companion (auto-installs when spp_source_tracking + spp_programs are both present), so spp_source_tracking no longer depends on spp_programs. - Base res_partner merge logic already guards program_membership_ids via hasattr, so it stays in the base module. - Guarded the 1.0 migration's spp_program_membership UPDATE behind a table-existence check. - Base source-tracking tests skip the program-membership cases when spp.program is absent; the companion affirms the extension. With #1081 + #1084, spp_api_v2, spp_api_v2_data and spp_source_tracking now install with no spp_programs (verified). spp_studio still pulls it via spp_audit (tracked in #1085).
Move the program/cycle audit rules (Program, Cycle, and the program manager rules) out of spp_audit into a new spp_audit_programs companion (auto-installs when spp_audit + spp_programs are both present), so spp_audit no longer depends on spp_programs. The base keeps only the Registry and Service Point rules. Also point the base self-protection / mail-thread audit tests at res.users instead of program models, so they pass without spp_programs. With this, spp_audit, spp_studio, spp_api_v2, spp_api_v2_data and spp_source_tracking all install with no spp_programs (verified: each installs alone with spp_programs uninstalled). Completes the #1080 decoupling.
There was a problem hiding this comment.
Code Review
This pull request refactors the codebase to decouple the base modules (spp_api_v2, spp_audit, spp_source_tracking, and spp_studio) from the spp_programs dependency by splitting program-specific features, configurations, and endpoints into new companion modules (spp_api_v2_programs, spp_audit_programs, spp_source_tracking_programs, and spp_studio_programs). These companion modules are configured to auto-install when both their base module and spp_programs are present, allowing registry-only deployments to run without the programs stack. Feedback on the changes highlights a critical migration risk in spp_source_tracking where executing an update query on spp_program_membership before the companion module's columns are created could trigger an UndefinedColumn database error; checking for the existence of the source_system column before running the migration is recommended.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| cr.execute( | ||
| """ | ||
| UPDATE spp_program_membership | ||
| SET source_system = 'v1-migration', | ||
| collection_method = 'migration', | ||
| collection_date = create_date | ||
| WHERE source_system IS NULL | ||
| """ | ||
| ) | ||
| membership_count = cr.rowcount | ||
| _logger.info( | ||
| "Updated %s existing program memberships with migration source tracking", | ||
| membership_count, | ||
| "SELECT 1 FROM information_schema.tables WHERE table_name = 'spp_program_membership'" | ||
| ) | ||
| if cr.fetchone(): | ||
| cr.execute( | ||
| """ | ||
| UPDATE spp_program_membership | ||
| SET source_system = 'v1-migration', | ||
| collection_method = 'migration', | ||
| collection_date = create_date | ||
| WHERE source_system IS NULL | ||
| """ | ||
| ) |
There was a problem hiding this comment.
Critical Migration Failure Risk
If spp_programs is installed, the table spp_program_membership will exist. However, during the migration phase of spp_source_tracking, the companion module spp_source_tracking_programs (which adds the source tracking columns to spp_program_membership) might not be installed or fully loaded yet.
Running the UPDATE query when the columns source_system, collection_method, and collection_date do not yet exist on spp_program_membership will raise an UndefinedColumn database error, causing the entire Odoo upgrade/startup process to fail.
To prevent this, we should defensively check if the columns actually exist in the table before executing the UPDATE query.
| cr.execute( | |
| """ | |
| UPDATE spp_program_membership | |
| SET source_system = 'v1-migration', | |
| collection_method = 'migration', | |
| collection_date = create_date | |
| WHERE source_system IS NULL | |
| """ | |
| ) | |
| membership_count = cr.rowcount | |
| _logger.info( | |
| "Updated %s existing program memberships with migration source tracking", | |
| membership_count, | |
| "SELECT 1 FROM information_schema.tables WHERE table_name = 'spp_program_membership'" | |
| ) | |
| if cr.fetchone(): | |
| cr.execute( | |
| """ | |
| UPDATE spp_program_membership | |
| SET source_system = 'v1-migration', | |
| collection_method = 'migration', | |
| collection_date = create_date | |
| WHERE source_system IS NULL | |
| """ | |
| ) | |
| cr.execute( | |
| """ | |
| SELECT 1 | |
| FROM information_schema.columns | |
| WHERE table_name = 'spp_program_membership' | |
| AND column_name = 'source_system' | |
| AND table_schema = current_schema() | |
| """ | |
| ) | |
| if cr.fetchone(): | |
| cr.execute( | |
| """ | |
| UPDATE spp_program_membership | |
| SET source_system = 'v1-migration', | |
| collection_method = 'migration', | |
| collection_date = create_date | |
| WHERE source_system IS NULL | |
| """ | |
| ) |
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## 19.0 #231 +/- ##
==========================================
+ Coverage 73.06% 73.10% +0.03%
==========================================
Files 1069 1106 +37
Lines 62120 63245 +1125
==========================================
+ Hits 45390 46233 +843
- Misses 16730 17012 +282
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
The #1083 decoupling exposed two latent couplings that crashed when spp_studio is installed without spp_programs (or when spp_programs is installed afterwards): 1. program_ids view references: spp_studio_change_requests and spp_studio_events displayed the shared spp.studio.mixin program_ids field, which now lives in the spp_studio_programs companion. Their base views failed to load without it. Dropped program_ids from both base views and added two auto-install companions (spp_studio_change_requests_programs, spp_studio_events_programs) that re-inject the field/page when programs are present. 2. spp.cel.expression.state selection: spp_studio replaced the shared selection, dropping the base 'active'/'inactive' values. This only worked when spp_programs loaded first (forced by the old dependency). Switched to selection_add so the field is the union of both state sets, making it load-order independent. spp_programs eligibility templates (state='active') now load regardless of install order. Also guarded TestDeferredResolutionProgramOverrides to skip when spp_programs is not installed (it accessed spp.cel.program.parameter in setUpClass).
…panion (#1081) CI runs each module's tests in isolation. TestScopeIsolation.setUp in spp_api_v2 created a program + membership for all its tests, which fails with KeyError: 'spp.program' when spp_api_v2 is tested without spp_programs. Only one test (program-membership scope isolation) needs programs, and it targets the /ProgramMembership endpoint that moved to spp_api_v2_programs in #1081. - Trim TestScopeIsolation.setUp to the resource types the base module serves (individual, group); the other 6 isolation tests need no program - Move test_individual_scope_does_not_grant_program_membership_access to spp_api_v2_programs (TestScopeEnforcementProgramMembership) Also apply pre-commit auto-format fixes that drifted on files from earlier commits in this branch (import order, line length, blank lines).
…#1081) Group first-party odoo.addons.spp_api_v2.* imports ahead of relative imports in the files moved from spp_api_v2 in #1081. Import-order only, no behavior change; fixes the pre-commit ruff check.
…#1081, #1083) The program-membership PUT update tests used url_open (which cannot issue a PUT) and only asserted [200, 405], so the update endpoint body was never exercised. Rewrite them to use url_put and add the missing paths: - update success (status change persisted) - 409 on stale If-Match, 404 on unknown membership, 400 on bad identifier - 422 create/update error paths (unknown program reference) - previous-page link on non-zero _offset Add a test for the pack-install wizard's _get_pack_program_id hook (selected program and the None branch). spp_api_v2_programs 86% -> 92%, spp_studio_programs 87% -> 93%. Test-only.
Why is this change needed?
Several core modules hard-depended on
spp_programs, so any starter bundle that included them installed the full Programs/Cycles/Entitlements stack — even registry-only deployments (Social Registry, Disability Registry) that don't use programs. This implements the #1080 umbrella: split the program-specific parts into thin*_programscompanions so the base modules no longer depend onspp_programs.Parent: #1080
How was the change implemented?
Each split follows the same pattern: a new
*_programscompanion that auto-installs when its base module +spp_programsare both present (so program functionality is preserved with zero loss), while the base module drops itsspp_programsdependency.spp_api_v2→spp_api_v2_programs: program/program_membership routers, services, schemas, filter configs, api.path records, and the program filter routers (registered into the sharedRESOURCE_SERVICES).spp_api_v2_data: no code change — it only pulledspp_programstransitively viaspp_api_v2; resolved by #1081 + #1084.spp_studio→spp_studio_programs: theprogram_idslink onspp.studio.mixinand the pack-install wizard'sprogram_id(routed via a_get_pack_program_id()hook).spp_source_tracking→spp_source_tracking_programs: thespp.program.membershipsource-tracking extension; the1.0migration's program UPDATE is guarded by a table-existence check.spp_audit→spp_audit_programs: the program/cycle audit rules (base keeps Registry + Service Point rules).New unit tests
Each companion ships tests for its program feature; base tests that used program models as incidental vehicles were made program-agnostic (e.g.
spp_auditself-protection tests now useres.users) or skip gracefully whenspp.programis absent.Unit tests executed by the author
Per-module installs, all green:
spp_api_v2+spp_api_v2_programs: 0 failed / 0 errors of 716spp_studio+spp_studio_programs: 0 / 228spp_source_tracking(+ companion): 0 / 46 alone, 0 / 47 with companionspp_auditalone: 0 / 34;spp_audit_programs: 0 / 1Decoupling verified — each base module installs alone with
spp_programs = uninstalled:spp_source_trackingalone →spp_programsuninstalledspp_studioalone →spp_programsuninstalled (spp_auditinstalled without it)spp_api_v2,spp_api_v2_data,spp_source_tracking,spp_studio,spp_auditpull nospp_programs.How to test manually
Install any base module alone (e.g.
spp_studio) and confirmspp_programsis not installed. Install it alongsidespp_programsand confirm the*_programscompanion auto-installs and program features work.Related links
#1080, #1081, #1082, #1083, #1084, #1085