Skip to content

feat(spp_vocabulary): manual codes UX on SYSTEM vocabularies (#954)#201

Open
emjay0921 wants to merge 4 commits into
19.0from
feat/954-system-vocab-code-readonly
Open

feat(spp_vocabulary): manual codes UX on SYSTEM vocabularies (#954)#201
emjay0921 wants to merge 4 commits into
19.0from
feat/954-system-vocab-code-readonly

Conversation

@emjay0921
Copy link
Copy Markdown
Contributor

Why is this change needed?

OP#954 — SYSTEM vocabularies (spp.vocabulary with is_system=True) ship immutable system codes via module data files. The backend already enforced this lock at the model layer (create / write / unlink guards in vocabulary_code.py), but the UI had three usability gaps:

  1. No visual cue for which codes are system-shipped vs admin-added — both rendered identically in lists and forms.
  2. No surfaced way to add a manual override — the existing is_local=True flag (ADR-016) was hidden, and any naïve attempt to add a code to a system vocab hit the backend UserError. Per QA feedback on the round-2 PR, this needed to be reworked to allow CRUD on manual codes added to a system vocab while keeping the lock on system codes.
  3. No way to filter / group the global code list by source — admins couldn't tell at a glance which codes were module-defined vs admin-added.

How was the change implemented?

Rounds 1–2 already locked the form / list toggles. This PR is round 3 — the manual-codes UX.

Model

  • New stored, indexed code_source Selection field on spp.vocabulary.code computed from is_local (system / manual). Reuses is_local as the source of truth — no data migration; the new field is just the readable label.
  • New action_add_manual_code method on spp.vocabulary that opens the code form with default_is_local=True + default_vocabulary_id pre-seeded, so admins don't need to know about the is_local flag.

Views

  • vocabulary_code_views.xml:
    • Source badge (green = Manual, blue = System) shown next to the code title in the form.
    • Removed restrictive [('is_system','=',False)] domain on vocabulary_id — manual codes can now reference system vocabularies.
    • Standalone code list: new Source column with badge, search-view filters System Codes / Manual Codes, Group-by Source.
  • vocabulary_views.xml (system vocab Codes tab):
    • Explicit "Add Manual Code" button above the embedded list, calling action_add_manual_code.
    • Inline list now shows the Source column. Inline create / delete stay disabled — manual codes always go through the button (so is_local=True is seeded consistently) and the standalone form (where the backend rules apply).

New unit tests

spp_vocabulary/tests/test_manual_codes.py — 5 focused tests:

  • test_code_source_computed_from_is_local — selection field derives correctly from is_local.
  • test_manual_code_create_allowed_on_system_vocabis_local=True codes can be created on a system vocab.
  • test_manual_code_identifying_fields_editablecode, display, definition stay editable on manual codes.
  • test_manual_code_can_be_deleted — manual codes can be unlinked.
  • test_action_add_manual_code_seeds_context — action context has default_is_local=True + default_vocabulary_id.

Unit tests executed by the author

Full spp_vocabulary suite ran locally in Docker: 0 failed, 0 error(s) of 198 tests.

How to test manually

See the QA round-3 comment on OP#954 for the full guide (https://projects.acn.fr/wp/954). Quick path:

  1. Reset DB, install spp_vocabulary.
  2. Open Settings → Vocabularies → Manage Vocabularies → Group Type (a SYSTEM vocab).
  3. Codes tab → click "Add Manual Code" → form opens with vocab pre-filled, Source = Manual. Enter households_local / Households (Local), save.
  4. Back on the Codes tab — new row shows Source = Manual, fully editable.
  5. Click an existing System code (e.g. household) — identifying fields are readonly, only active / deprecated / sequence editable.
  6. Try to delete the system code from its form's Action menu — backend blocks with the existing UserError. Try to delete the manual code — succeeds.
  7. Open Manage Codes, verify the new Source column + filters System Codes / Manual Codes.

Related links

System vocabularies are loaded via XML by modules / data files and
shouldn't be reshaped by users at runtime — but until now the form
allowed editing structural fields (name, namespace, domain, version,
hierarchical flag, etc.) and the inline Codes list let users add or
delete codes that downstream code paths key off of. This now matches
the existing Python guards in `models/vocabulary_code.py` (line 191,
279, 437, 594) so users see what they can / can't change rather than
hitting a server-side UserError after typing.

Vocabulary form:
- Hide the `is_system` toggle so it can no longer be flipped manually
  (the orange SYSTEM ribbon stays as the visual indicator).
- Lock `name`, `namespace_uri`, `domain`, `version`, `is_hierarchical`,
  `reference_url`, `description`, and `active` when `is_system=True`.

Codes tab on the vocabulary form: split into two `code_ids` blocks —
the non-system one keeps the original full-edit list; the system one
sets `create="false" delete="false"` on the inline list and locks
identifying columns (`code`, `display`, `definition`, `parent_id`)
while sequence handle / `deprecated` / `active` stay flippable. Local
codes (`is_local=True`) keep their pre-existing edit behaviour via the
`parent.is_system and not is_local` condition.

Vocabulary code form: same per-field lock applied to `display`, `code`,
`vocabulary_id`, `parent_id`, `target_type`, and `definition` — adds a
hidden `is_local` field so the readonly expressions can reference it.

Tests: spp_vocabulary suite still 0 failed, 0 errors of 193.
…(#954)

The Vocabularies list view exposed `is_system` and `active` as
boolean_toggle columns, so admins could flip a vocabulary to/from
system status or archive it directly from the list. System status
should only ever be set by a module's data files, and archiving
should go through the form (where it's already gated to non-system
vocabs after the previous round). Conditional readonly per row would
be ideal but is out of scope for this round — making both columns
unconditionally readonly is the safe interim fix.
System vocabularies already accepted is_local=True codes at the model layer
but the UI made the manual-vs-system distinction invisible:

- Add a stored, indexed `code_source` Selection field on spp.vocabulary.code
  derived from `is_local` ('system' / 'manual'). No data migration — keeps
  is_local as the source of truth (ADR-016).
- Add `action_add_manual_code` on spp.vocabulary. Opens the code form with
  default_is_local=True + default_vocabulary_id pre-seeded so admins can
  intentionally layer manual codes onto a SYSTEM vocab without tripping the
  create-guard in vocabulary_code.py.
- Form view (vocabulary_code_views.xml): show Source as a coloured badge
  next to the code title; drop the restrictive `[('is_system','=',False)]`
  domain on vocabulary_id so manual codes can reference system vocabularies.
- Standalone code list + search: Source column with badge, System / Manual
  filters, Group-by Source.
- Vocabulary form Codes tab (system case): explicit "Add Manual Code"
  button above the embedded list; new Source column in the inline list so
  admins can see at a glance which codes are module-shipped vs admin-added.
  Inline create/delete stay disabled — manual codes go through the button
  + form so is_local is correctly set; system-code deletion is still
  blocked at the backend.

Adds tests covering code_source compute, manual code create/edit/delete on
a system vocab, and action_add_manual_code context seeding.
@codecov
Copy link
Copy Markdown

codecov Bot commented May 14, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 72.08%. Comparing base (b3510d8) to head (b2411f2).

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             19.0     #201      +/-   ##
==========================================
+ Coverage   66.08%   72.08%   +6.00%     
==========================================
  Files          86      356     +270     
  Lines        7501    21802   +14301     
==========================================
+ Hits         4957    15716   +10759     
- Misses       2544     6086    +3542     
Flag Coverage Δ
spp_alerts 96.77% <ø> (?)
spp_analytics 93.13% <ø> (?)
spp_api_v2 80.33% <ø> (?)
spp_api_v2_change_request 66.85% <ø> (?)
spp_api_v2_cycles 71.12% <ø> (?)
spp_api_v2_data 64.41% <ø> (?)
spp_api_v2_entitlements 70.19% <ø> (?)
spp_api_v2_gis 71.52% <ø> (?)
spp_api_v2_products 66.27% <ø> (?)
spp_api_v2_service_points 70.94% <ø> (?)
spp_api_v2_simulation 71.12% <ø> (?)
spp_api_v2_vocabulary 57.26% <ø> (?)
spp_approval 50.29% <ø> (?)
spp_area 80.07% <ø> (?)
spp_area_hdx 81.43% <ø> (?)
spp_audit 72.60% <ø> (?)
spp_base_common 90.26% <ø> (ø)
spp_programs 64.84% <ø> (+0.01%) ⬆️
spp_security 66.66% <ø> (ø)
spp_vocabulary 80.23% <100.00%> (?)

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
spp_vocabulary/models/vocabulary.py 82.81% <100.00%> (ø)
spp_vocabulary/models/vocabulary_code.py 94.17% <100.00%> (ø)

... and 276 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces the ability to add manual codes to system vocabularies. It includes a new code_source field to distinguish between system-shipped and manually added codes, updates views to enforce read-only constraints on system data while allowing edits to manual overrides, and adds a dedicated action for creating manual codes. Feedback was provided to improve the list view by using conditional read-only logic for the active field, allowing users to archive custom vocabularies directly from the list while maintaining protection for system vocabularies.

Comment on lines +14 to +22
<!-- Read-only in the list: system status is set by the
module's data files, not user-toggleable; and
archiving a vocabulary should go through the form
(where it's already gated to non-system vocabs).
Conditional readonly per row would be ideal here but
is not in scope for this round — see OP#954. -->
<field name="is_system" widget="boolean_toggle" readonly="1" />
<field name="is_hierarchical" optional="hide" />
<field name="active" widget="boolean_toggle" />
<field name="active" widget="boolean_toggle" readonly="1" />
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

In Odoo 17, you can implement the "ideal" conditional read-only behavior you mentioned by using readonly="is_system" on the active field. This allows users to archive custom vocabularies directly from the list view while keeping system vocabularies protected. I've also updated the comment to reflect this change.

Suggested change
<!-- Read-only in the list: system status is set by the
module's data files, not user-toggleable; and
archiving a vocabulary should go through the form
(where it's already gated to non-system vocabs).
Conditional readonly per row would be ideal here but
is not in scope for this round — see OP#954. -->
<field name="is_system" widget="boolean_toggle" readonly="1" />
<field name="is_hierarchical" optional="hide" />
<field name="active" widget="boolean_toggle" />
<field name="active" widget="boolean_toggle" readonly="1" />
<!-- Read-only in the list: system status is set by the
module's data files, not user-toggleable.
Archiving a vocabulary is allowed in the list for
custom vocabularies via conditional readonly.
See OP#954. -->
<field name="is_system" widget="boolean_toggle" readonly="1" />
<field name="is_hierarchical" optional="hide" />
<field name="active" widget="boolean_toggle" readonly="is_system" />

@emjay0921
Copy link
Copy Markdown
Contributor Author

@gonzalesedwin1123 — ready for review.

All CI checks are green. Clean merge with 19.0 (no conflicts). Round-3 brings manual codes UX on system vocabularies (Code Source badge, "Add Manual Code" button, filters / group-by). Tests: 5 new in test_manual_codes.py, 0 failed of 198 in the full spp_vocabulary suite locally.

OP#954: https://projects.acn.fr/wp/954

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.

1 participant